/*
 * This file is part of the Refran Library
 * Copyright (C) 2009 César Enrique García
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/** @file
 * $Author: cquike@gmail.com $
 * $Date: 2011-11-23 12:45:52 +0000 (Wed, 23 Nov 2011) $
 * $Revision: 242 $
 */

#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE RecipeFactorySuite
#define _GLIBCXX_USE_NANOSLEEP
#include <iostream>
#include <fstream>
#include <boost/test/unit_test.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/thread.hpp>
#include "RecipeFactory.h"
#include "DummyRecipe.h"
#include "Error.h"


BOOST_AUTO_TEST_SUITE(RecipeFactorySuite)

BOOST_AUTO_TEST_CASE(instantiateRecipeFactory)
{
    /* Factory instantiation */
    Refran::RecipeFactory& factory = Refran::RecipeFactory::instance();
    BOOST_REQUIRE(&factory != 0);
    BOOST_REQUIRE_EQUAL(factory.getRecipeNameList().size(), 0);
}

BOOST_AUTO_TEST_CASE(addingRecipeCreators)
{
    /* Adding recipes creators */
    Refran::RecipeFactory& factory = Refran::RecipeFactory::instance();
    std::string recipeName("DummyRecipe");
    factory.insertCreator<DummyRecipe>(recipeName);
    BOOST_REQUIRE_EQUAL(factory.getRecipeNameList().size(), 1);
    BOOST_REQUIRE_EQUAL(factory.getRecipeNameList()[0], recipeName);
}

BOOST_AUTO_TEST_CASE(addingRecipeCreatorsVersion)
{
    /* Adding recipes creators */
    Refran::RecipeFactory& factory = Refran::RecipeFactory::instance();
    factory.clear();
    std::string recipeName("DummyRecipe");
    factory.insertCreator<DummyRecipe>(recipeName, 1);
    BOOST_REQUIRE_EQUAL(factory.getRecipeNameList().size(), 1);
    BOOST_REQUIRE_EQUAL(factory.getRecipeNameList()[0], recipeName);
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE(RecipeFactoryBenchmarksSuite)

BOOST_AUTO_TEST_CASE(addingRemovingRecipeCreatorBenchmark)
{
    /* Adding many recipes creators */
    int nExec = 100000;
    Refran::RecipeFactory& factory = Refran::RecipeFactory::instance();
    factory.clear();
    std::string recipeName("DummyRecipe");
    for(int i = 0; i < nExec ; ++i)
    {
        factory.insertCreator<DummyRecipe>(recipeName);
        factory.removeCreator(recipeName);
    }
    BOOST_CHECK(true);
}

BOOST_AUTO_TEST_CASE(addingManyRecipeCreatorBenchmark)
{
    /* Adding many recipes creators */
    int nExec = 100000;
    Refran::RecipeFactory& factory = Refran::RecipeFactory::instance();
    factory.clear();
    for(int i = 0; i < nExec ; ++i)
        factory.insertCreator<DummyRecipe>(boost::lexical_cast<std::string>(i));
    BOOST_CHECK(true);
}

BOOST_AUTO_TEST_CASE(getListManyRecipesBenchmark)
{
    /* Get list of recipes */
    Refran::RecipeFactory& factory = Refran::RecipeFactory::instance();
    std::vector<std::string> recipeList = factory.getRecipeNameList();
    BOOST_CHECK(true);
}

BOOST_AUTO_TEST_CASE(creatingRecipeLargeRecipeListBenchmark)
{
    int nExec = 100000;
    Refran::RecipeFactory& factory = Refran::RecipeFactory::instance();
    std::string recipeName("1000");
    for(int i = 0; i < nExec ; ++i)
    {
        std::shared_ptr<Refran::Recipe> recipe =
            factory.createRecipe(recipeName);
    }
    BOOST_CHECK(true);
}

BOOST_AUTO_TEST_CASE(clearingManyRecipeCreatorBenchmark)
{
    Refran::RecipeFactory& factory = Refran::RecipeFactory::instance();
    factory.clear();
    BOOST_CHECK(true);
}

BOOST_AUTO_TEST_CASE(addingManyRecipeVersionCreatorBenchmark)
{
    /* Adding many recipes creators */
    int nExec = 100000;
    Refran::RecipeFactory& factory = Refran::RecipeFactory::instance();
    factory.clear();
    std::string recipeName("DummyRecipe");
    for(int i = 0; i < nExec ; ++i)
        factory.insertCreator<DummyRecipe>(recipeName, i);
    BOOST_CHECK(true);
}

BOOST_AUTO_TEST_CASE(addingRecipeCreatorAndReadingRecipeVersionListenchmark)
{
    /* This test adds recipe creators to a RecipeFactory with many
       RecipeCreators while at the same time, some other threads are accessing
       the list of RecipeCreators (which is much more expensive).
       After all the threads which add RecipeCreator's finish, we disregard
       the threads which where accessing the list of RecipeCreators.
       This will allow to test whether createRecipe() is blocked by 
       getRecipeVersions() */
    int nExec = 10000;
    Refran::RecipeFactory& factory = Refran::RecipeFactory::instance();
    std::string recipeName("OtherDummyRecipe");
    boost::thread_group recipeInsertersAndRemoversPool;
    boost::thread_group recipeListGettersPool;
    for(int i = 0; i < nExec ; ++i)
    {
        recipeInsertersAndRemoversPool.create_thread(
            std::bind(&Refran::RecipeFactory::insertCreator<DummyRecipe>,
                      &factory,recipeName,i));
        recipeListGettersPool.create_thread(
            std::bind(&Refran::RecipeFactory::getRecipeNameList,&factory));
    }
    /* Wait for the recipe inserters to finish */
    recipeInsertersAndRemoversPool.join_all();

    /* Remove all the inserted creators to leave 
       the factory in the same state as before */
    factory.removeCreator(recipeName);

    /* Kill all the getRecipeVersions threads: */
    recipeListGettersPool.interrupt_all();

    BOOST_CHECK(true);
}

BOOST_AUTO_TEST_CASE(clearingManyRecipeVersionsCreatorBenchmark)
{
    Refran::RecipeFactory& factory = Refran::RecipeFactory::instance();
    factory.clear();
    BOOST_CHECK(true);
}

BOOST_AUTO_TEST_SUITE_END()

/**
 * @test
 */
struct OneRecipeFactoryFixture
{
    OneRecipeFactoryFixture() :
        factory(Refran::RecipeFactory::instance()),
        dummyRecipeName("DummyRecipe")
    {

        BOOST_TEST_MESSAGE( "creating simple factory" );
        BOOST_REQUIRE_NO_THROW(factory.clear());
        BOOST_REQUIRE_NO_THROW(factory.insertCreator<DummyRecipe>(dummyRecipeName));
        BOOST_REQUIRE_EQUAL(factory.getRecipeNameList().size(), 1);
        BOOST_TEST_MESSAGE( "setup simple factory" );
    }

    ~OneRecipeFactoryFixture()
    {
        BOOST_TEST_MESSAGE("teardown simple factory" );
    }

    void clear()
    {
        factory.clear();
    }

    Refran::RecipeFactory& factory;

    std::string dummyRecipeName;
};

BOOST_FIXTURE_TEST_SUITE(RecipeFactoryOneRecipeFactorySuite, 
                         OneRecipeFactoryFixture)

BOOST_AUTO_TEST_CASE(addingDuplicatedCreator)
{
    /* Adding a duplicated recipes creators */
    std::string recipeName("DummyRecipe");
    BOOST_REQUIRE_THROW(factory.insertCreator<DummyRecipe>(recipeName),
                        Refran::Error);
}

BOOST_AUTO_TEST_CASE(getListRecipes)
{
    /* Get list of recipes */
    std::vector<std::string> recipeList = factory.getRecipeNameList();
    BOOST_REQUIRE_EQUAL(recipeList.size(), 1);
}

BOOST_AUTO_TEST_CASE(creatingRecipe)
{
    /* Getting a Recipe */
    std::shared_ptr<Refran::Recipe> recipe =
        factory.createRecipe(dummyRecipeName);
    BOOST_REQUIRE(recipe.unique());
}

BOOST_AUTO_TEST_CASE(creatingRecipeBenchmark)
{
    int nExec = 100000;
    for(int i = 0; i < nExec ; ++i)
    {
        std::shared_ptr<Refran::Recipe> recipe =
            factory.createRecipe(dummyRecipeName);
    }
}

BOOST_AUTO_TEST_CASE(creatingRecipeVersion)
{
    /* Getting a Recipe */
    factory.insertCreator<DummyRecipe>(dummyRecipeName, 1);
    std::shared_ptr<Refran::Recipe> recipe =
            factory.createRecipe(dummyRecipeName, 1);
    BOOST_REQUIRE(recipe.unique());
}

BOOST_AUTO_TEST_CASE(getRecipeVersions)
{
    /* Getting a Recipe */
    factory.insertCreator<DummyRecipe>(dummyRecipeName, 1);
    BOOST_REQUIRE_EQUAL(factory.getRecipeVersions(dummyRecipeName).size(), 2);
    BOOST_REQUIRE_EQUAL(factory.getRecipeVersions(dummyRecipeName)[0], 0);
    BOOST_REQUIRE_EQUAL(factory.getRecipeVersions(dummyRecipeName)[1], 1);
}

BOOST_AUTO_TEST_CASE(creatingInexistentRecipe)
{
    BOOST_REQUIRE_THROW(std::shared_ptr<Refran::Recipe> otherRecipe =
        factory.createRecipe("InexistentRecipe"),
        Refran::Error);
}

BOOST_AUTO_TEST_CASE(deletingCreator)
{
    /* Deleting a creator */
    int nRecipesBefore = factory.getRecipeNameList().size();
    factory.removeCreator(dummyRecipeName);
    BOOST_REQUIRE_EQUAL(factory.getRecipeNameList().size(), nRecipesBefore - 1);
}

BOOST_AUTO_TEST_CASE(removeCreatorVersions)
{
    /* Getting a Recipe */
    int nRecipesBefore = factory.getRecipeVersions(dummyRecipeName).size();
    factory.insertCreator<DummyRecipe>(dummyRecipeName, 4);
    factory.removeCreator(dummyRecipeName,4);
    BOOST_REQUIRE_EQUAL(factory.getRecipeNameList().size(), nRecipesBefore);
}

BOOST_AUTO_TEST_CASE(deletingInexistentCreator)
{
    BOOST_REQUIRE_THROW(factory.removeCreator("InexistentRecipe"),
                        Refran::Error);
}

BOOST_AUTO_TEST_CASE(deletingAllCreators)
{
    /* Clearing the recipe creators */
    factory.clear();
    BOOST_REQUIRE_EQUAL(factory.getRecipeNameList().size(), 0);
}

BOOST_AUTO_TEST_CASE(shutdown)
{
    Refran::RecipeFactory::shutdown();
    BOOST_REQUIRE_EQUAL(Refran::RecipeFactory::instance().getRecipeNameList().size(),
                        0);
    /* Shutdown again. Should have null effect */
    Refran::RecipeFactory::shutdown();
    BOOST_REQUIRE_EQUAL(Refran::RecipeFactory::instance().getRecipeNameList().size(),
                        0);
}

void instantiateShutdownRecipeFactory()
{
    Refran::RecipeFactory& factory = Refran::RecipeFactory::instance();
    Refran::RecipeFactory::shutdown();
}

BOOST_AUTO_TEST_CASE(instantiateShutdownRecipeFactoryMultithread)
{
    for(int i = 0; i< 10; ++i)
    {
        boost::thread_group pool;
        for(int j = 0; j< 100; ++j)
            pool.create_thread(instantiateShutdownRecipeFactory);
        pool.join_all();
    }
}

BOOST_AUTO_TEST_CASE(clearMultithread)
{
    for(int i = 0; i< 10; ++i)
    {
        boost::thread_group pool;
        for(int j = 0; j< 100; ++j)
            pool.create_thread
            (boost::bind(&OneRecipeFactoryFixture::clear, this));
        pool.join_all();
    }
}

BOOST_AUTO_TEST_SUITE_END()
